package com.atguigu.distributed.lock.service;

import com.atguigu.distributed.lock.mapper.StockMapper;
import com.atguigu.distributed.lock.pojo.Stock;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 简单锁示例
 */

@Service
//@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class StockService {

    private ReentrantLock lock = new ReentrantLock();


    @Autowired
    private StockMapper stockMapper;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    /**
     * redis乐观锁
     */
    public void deduct4() {
        this.stringRedisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                operations.watch("stock");
                // 1. 查询库存信息
                Object stock = operations.opsForValue().get("stock");
                // 2. 判断库存是否充足
                int st = 0;
                if (stock != null && (st = Integer.parseInt(stock.toString())) > 0) {
                    // 3. 扣减库存
                    operations.multi();
                    operations.opsForValue().set("stock", String.valueOf(--st));
                    //exec 执行事务
                    List exec = operations.exec();
                    //如果执行事务的返回结果集为空，则代表减库存失败，重试
                    if (exec == null || exec.size() == 0) {
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        deduct4();
                    }
                    return exec;
                }
                return null;
            }
        });


        //以下是错误用法

        //watch
        /*this.stringRedisTemplate.watch("stock");
        //1.查询库存信息
        String stock = this.stringRedisTemplate.opsForValue().get("stock");

        //2.判断库存是否充足
        if (stock != null && stock.length() != 0) {
            Integer st = Integer.valueOf(stock);
            if (st > 0) {
                //multi
                this.stringRedisTemplate.multi();
                //3.扣减库存
                this.stringRedisTemplate.opsForValue().set("stock", String.valueOf(--st));
                //exec
                this.stringRedisTemplate.exec();
            }
        }*/
    }


    /**
     * 乐观锁
     */
    // @Transactional
    public void deduct3() throws InterruptedException {

        //1.查询库存信息
        List<Stock> stockList = this.stockMapper.selectList(new LambdaQueryWrapper<Stock>().eq(Stock::getProductCode, "1001"));
        // 这里都第一个库存
        Stock stock = stockList.get(0);

        //2.判断库存是否充足
        if (stock != null && stock.getCount() > 0) {
            //3.扣减库存
            stock.setCount(stock.getCount() - 1);
            int version = stock.getVersion();
            stock.setVersion(version + 1);
            if (this.stockMapper.update(stock, new LambdaUpdateWrapper<Stock>().eq(Stock::getVersion, version)) == 0) {
                // 如果更新失败，则进行重试
                Thread.sleep(20);
                this.deduct3();
                //1、容易报栈溢出错误，因为这里一直递归，加Thread.sleep(20)，减少重试次数(间隔大了，执行这条sql时间错开了)，减少栈溢出
                //2、数据库连接超时错误，因为这里的update(增删改会加锁)会在这个事务里面阻塞，30秒后会报连接超时错误
                //如果去掉@Transactional事务注解，就会在这条sql跑完后锁释放（一个sql一个事务）
            }
        }


    }

    /**
     * 悲观锁
     */
    @Transactional
    public void deduct2() {
        //1.查询库存信息并锁定库存信息
        List<Stock> stockList = this.stockMapper.queryStock("1001");
        // 这里都第一个库存
        Stock stock = stockList.get(0);

        //2.判断库存是否充足
        if (stock != null && stock.getCount() > 0) {
            //3.扣减库存
            stock.setCount(stock.getCount() - 1);
            this.stockMapper.updateById(stock);
        }


    }


    /**
     * JVM锁
     */
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    //@Transactional(isolation = Isolation.SERIALIZABLE)//会死锁
    public void deduct1() {
        //lock.lock();
        try {
            //update insert delete写操作本身就会加锁(悲观锁)
            //update db_stock set count = count - 1 where product_code = '1001' and id =1 and count >=1
            //stockMapper.updateStock("1001", 1, 1);

            /*LambdaQueryWrapper<Stock> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Stock::getProductCode, "1001");
            queryWrapper.eq(Stock::getId, 1);
            Stock stock = stockMapper.selectOne(queryWrapper);*/

            Stock stock = stockMapper.selectOne(new QueryWrapper<Stock>().eq("product_code", "1001").eq("id", 1));
            if (stock != null && stock.getCount() > 0) {
                stock.setCount(stock.getCount() - 1);
                stockMapper.updateById(stock);
            }
        } finally {
            //lock.unlock();
        }
    }

}
